#include "csvWriter.h"

void ReleaseCCSVWriter(CsvWriter writer)
{
    if (writer != NULL)
    {
        if (writer->rawBuffer)
        {
            free(writer->rawBuffer);
        }
        free(writer);
    }
}

void UseCRLF(CsvWriter w)
{
    w->useCRLF = TRUE;
    return;
}

CsvWriter NewCCSVWriter(FILE *fp)
{
    CsvWriter writer = NULL;
    if (fp != NULL)
    {
        writer = malloc(sizeof(*writer));
        if (writer != NULL)
        {
            memset(writer, 0, sizeof(*writer));
            writer->comma = ',';
            writer->fp = fp;
            writer->rawBufferSize = defaultRawBufferSize;
            writer->rawBuffer = malloc(defaultRawBufferSize * sizeof(*writer->rawBuffer));
            if (writer->rawBuffer != NULL)
            {
                memset(writer->rawBuffer, 0, defaultRawBufferSize * sizeof(*writer->rawBuffer));
                goto _exit;
            }
        }
    }
_error:
    ReleaseCCSVWriter(writer);
    writer = NULL;
    goto _exit;
_exit:
    return writer;
}

//try to add raw size, if the size will be out of limit, back off and return error.
CCSVWriteError preAddRawSize(CsvWriter w, int size)
{
    int ret = CCSVWriteErrorWriterNoError;
    w->rawLen += size;
    if (w->rawLen > w->rawBufferSize)
    {
        w->rawLen -= size;
        ret = CCSVWriteErrorRawSizeOutOfLimit;
    }

    return ret;
}

static BOOL charNeedsQuotes(CsvWriter w, char ch)
{
    if (ch == w->comma || ch == '"' || ch == '\r' || ch == '\n' || isspace(ch))
    {
        return TRUE;
    }
    if (ch == '\0')
    {
        return FALSE;
    }
    return FALSE;
}

static int needQuotesWriteCache(CsvWriter w, char *ptr, char **cachPos)
{
    int ret = 0;

    while (*ptr != '\0')
    {
        if (charNeedsQuotes(w, *ptr) == TRUE)
        {
            if (*w->nextFieldIndex != '"')
            {
                ret = preAddRawSize(w, 1);
                if (ret)
                {
                    goto _exit;
                }
                memcpy(w->nextFieldIndex + 1, w->nextFieldIndex, (*cachPos) - w->nextFieldIndex + 1);
                (*cachPos)++;
                *(w->nextFieldIndex) = '"';
            }

            switch (*ptr)
            {
            case '"':
                ret = preAddRawSize(w, 2);
                if (ret)
                {
                    goto _exit;
                }
                *((*cachPos)++) = '"';
                *((*cachPos)++) = *ptr;
                break;
            case ',':
                //comma as comma
                ret = preAddRawSize(w, 1);
                if (ret)
                {
                    goto _exit;
                }
                *((*cachPos)++) = *ptr;
                break;
            case '\r':
                //the '\r' in the raw
                if (!(w->useCRLF))
                {
                    ret = preAddRawSize(w, 1);
                    if (ret)
                    {
                        goto _exit;
                    }
                    *((*cachPos)++) = '\r';
                }
                break;
            case '\n':
                //the '\n' in the raw
                if (w->useCRLF)
                {
                    ret = preAddRawSize(w, 2);
                    if (ret)
                    {
                        goto _exit;
                    }
                    *((*cachPos)++) = '\r';
                    *((*cachPos)++) = '\n';
                }
                else
                {
                    ret = preAddRawSize(w, 1);
                    if (ret)
                    {
                        goto _exit;
                    }
                    *((*cachPos)++) = '\n';
                }
                break;
            default:
                *((*cachPos)++) = *ptr;
                break;
            }
        }
        else
        {
            ret = preAddRawSize(w, 1);
            if (ret)
            {
                goto _exit;
            }
            *((*cachPos)++) = *ptr;
        }
        ptr++;
    }
    if (*(w->nextFieldIndex) == '"')
    {
        ret = preAddRawSize(w, 1);
        if (ret)
        {
            goto _exit;
        }
        *((*cachPos)++) = '"';
    }
_exit:
    return ret;
}

static int notNeedQuotesWriteCache(CsvWriter w, char *ptr, char **cachPos)
{
    int ret = 0;
    while (*ptr != '\0')
    {
        ret = preAddRawSize(w, 1);
        if (ret)
        {
            goto _exit;
        }
        *((*cachPos)++) = *ptr;
        ptr++;
    }
_exit:
    return ret;
}
//Write writes a single CSV record to w along with any necessary quoting.
//A record is a CsvRecord.
//Writes are buffered, we also support Flush func as the syscall flush.
CCSVWriteError
CCSVWrite(CsvWriter w, CsvRecord r)
{
    int ret = 0, i = 0;
    char *pos = NULL;
    char *ptr = NULL;
    if (w == NULL || r == NULL)
    {
        ret = CCSVWriteErrorArgErr;
        goto _exit;
    }
    if (!validDelim(w->comma))
    {
        ret = CCSVWriteErrorWriterAttrError;
        goto _exit;
    }

    //initialize the rawbuffer
    pos = w->rawBuffer;
    w->rawLen = 0;
    w->nextFieldIndex = w->rawBuffer;

    for (i = 0; i < r->num; i++)
    {
        if (i > 0)
        {
            ret = preAddRawSize(w, 1);
            if (ret)
            {
                goto _exit;
            }
            *(pos++) = w->comma;
            w->nextFieldIndex = pos;
            //the begin of new line should be '\0', we complete it then
            *(w->nextFieldIndex) = '\0';
        }

        ptr = r->elements[i];
        if (r->fleeFlag && r->fleeFlag[i] == TRUE)
        {
            ret = notNeedQuotesWriteCache(w, ptr, &pos);
            if (ret)
            {
                goto _exit;
            }
        }
        else
        {
            ret = needQuotesWriteCache(w, ptr, &pos);
            if (ret)
            {
                goto _exit;
            }
        }
    }

    //at last, add '\n' or '\r\n'
    if (w->useCRLF)
    {
        ret = preAddRawSize(w, 2);
        if (ret)
        {
            goto _exit;
        }
        *(pos++) = '\r';
        *(pos++) = '\n';
    }
    else
    {
        ret = preAddRawSize(w, 1);
        if (ret)
        {
            goto _exit;
        }
        *(pos++) = '\n';
    }
    ret = preAddRawSize(w, 1);
    if (ret)
    {
        goto _exit;
    }
    *(pos++) = '\0';
    fputs(w->rawBuffer, w->fp);
_exit:
    return ret;
}